package model

import (
	j_log "config-client-v1/init/j-log"
	"config-client-v1/utils/tools"
	"errors"
	"fmt"
	"github.com/goccy/go-json"
	"runtime/debug"
	"sync"
)

var RunningExpPage = &RunningExpPageStu{M: make(map[int]*Page)}
var ExpAddrMap = &ExpAddrMapStu{M: make(map[string]bool)}

type Page struct {
	ID            int           `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
	ProjectID     int           `gorm:"column:project_id" json:"project_id"`
	GroupID       int           `gorm:"column:group_id" json:"group_id"`
	PageAttribute int           `gorm:"column:page_attribute" json:"page_attribute"`
	PageName      string        `gorm:"column:page_name" json:"page_name"`
	Content       string        `gorm:"column:content" json:"content"`
	ContentCeshi  string        `gorm:"column:content_ceshi" json:"content_ceshi"`
	Canvas        string        `gorm:"column:canvas" json:"canvas"`
	ExpData       string        `gorm:"column:exp_data" json:"exp_data"`
	SeriesColors  string        `gorm:"column:seriesColors" json:"seriesColors"`
	ReportType    int           `gorm:"column:report_type" json:"report_type"`
	IsDefault     uint8         `gorm:"column:is_default" json:"is_default"`
	OriginID      int           `gorm:"column:origin_id" json:"origin_id"`
	CreateTime    *string       `gorm:"column:create_time" json:"create_time"`
	UpdateTime    *string       `gorm:"column:update_time" json:"update_time"`
	Del           uint8         `gorm:"column:del" json:"del"`
	ExpDataList   []*ExpDataObj `json:"-" gorm:"-"` // exp_data 展开结构
}

func (m *Page) TableName() string {
	return "page"
}

func (m *Page) Exists(id int) (exist bool, err error) {
	err = DB.Where("id = ?", id).Limit(1).Find(m).Error
	if err != nil {
		return
	}
	if m.ID == 0 {
		return
	}
	exist = true
	return
}

type PageGroup struct {
	ID         int     `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
	ProjectID  int     `gorm:"column:project_id" json:"project_id"`
	GroupName  string  `gorm:"column:group_name" json:"group_name"`
	CreateTime *string `gorm:"column:create_time" json:"create_time"`
	UpdateTime *string `gorm:"column:update_time" json:"update_time"`
	Del        uint8   `gorm:"column:del" json:"del"`
	PageList   []*Page `json:"page_list"  gorm:"foreignKey:GroupID;references:ID"` // foreignKey 外表外键 references 当前表关联键
}

func (m *PageGroup) TableName() string {
	return "page_group"
}

func (m *PageGroup) Exists(id int) (exist bool, err error) {
	err = DB.Where("id = ?", id).Limit(1).Find(m).Error
	if err != nil {
		return
	}
	if m.ID == 0 {
		return
	}
	exist = true
	return
}

type PageReportForm struct {
	ID           int     `gorm:"column:id" json:"id"`
	ProjectID    int     `gorm:"column:project_id" json:"project_id"`
	PageID       int     `gorm:"column:page_id" json:"page_id" binding:"required"`
	PageGroupID  int     `gorm:"column:page_group_id" json:"page_group_id"`
	TableHeaders string  `gorm:"column:tableHeaders" json:"tableHeaders"`
	TableData    string  `gorm:"column:tableData" json:"tableData"`
	CreateTime   *string `gorm:"column:create_time" json:"create_time"`
	UpdateTime   *string `gorm:"column:update_time" json:"update_time"`
}

func (m *PageReportForm) TableName() string {
	return "page_report_form"
}

func (m *PageReportForm) Exists(id int) (exist bool, err error) {
	err = DB.Where("id = ?", id).Limit(1).Find(m).Error
	if err != nil {
		return
	}
	if m.ID == 0 {
		return
	}
	exist = true
	return
}

type PageReportLog struct {
	ID          int     `gorm:"column:id" json:"id"`
	ProjectID   int     `gorm:"column:project_id" json:"project_id"`
	PageID      int     `gorm:"column:page_id" json:"page_id"`
	PageName    string  `gorm:"column:page_name" json:"page_name"`
	Base64Image string  `gorm:"column:base64_image" json:"base64_image"`
	Operator    string  `gorm:"column:operator" json:"operator"`
	OperateTime string  `gorm:"column:operate_time" json:"operate_time"`
	ReportTime  string  `gorm:"column:report_time" json:"report_time"`
	UID         int     `gorm:"column:uid" json:"uid"`
	CreateTime  *string `gorm:"column:create_time" json:"create_time"`
}

func (m *PageReportLog) TableName() string {
	return "page_report_log"
}

type ComboBox struct {
	ID         int     `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
	ProjectID  int     `gorm:"column:project_id" json:"project_id"`
	Name       string  `gorm:"column:name" json:"name"`
	Content    string  `gorm:"column:content" json:"content"`
	UID        int     `gorm:"column:uid" json:"uid"`
	CreateTime *string `gorm:"column:create_time" json:"create_time"`
}

func (m *ComboBox) TableName() string {
	return "page_combo_box"
}

func (m *ComboBox) Exists(id int) (exist bool, err error) {
	err = DB.Where("id = ?", id).Limit(1).Find(m).Error
	if err != nil {
		return
	}
	if m.ID == 0 {
		return
	}
	exist = true
	return
}

type ExpDataItem struct {
	Name string  `json:"name"`
	Addr *string `json:"addr,omitempty"`
}

type ExpDataObj struct {
	Name            string         `json:"name"`
	Type            string         `json:"type"`
	ItemList        []*ExpDataItem `json:"item_list"`
	UpperLimitValue *float64       `json:"upper_limit_value,omitempty"`
	LowerLimitValue *float64       `json:"lower_limit_value,omitempty"`
}

type RunningExpPageStu struct {
	M map[int]*Page // Key为page_id
	sync.RWMutex
}

func (m *RunningExpPageStu) Set(key int, value *Page) {
	m.Lock()
	defer m.Unlock()
	m.M[key] = value
}

func (m *RunningExpPageStu) Get(key int) (value *Page, ok bool) {
	m.RLock()
	defer m.RUnlock()
	value, ok = m.M[key]
	return
}

func (m *RunningExpPageStu) Delete(key int) {
	m.Lock()
	defer m.Unlock()
	delete(m.M, key)
}

type ExpAddrMapStu struct {
	M map[string]bool // key为addr
	sync.RWMutex
}

func (m *ExpAddrMapStu) Set(key string) {
	m.Lock()
	defer m.Unlock()
	m.M[key] = true
}

func (m *ExpAddrMapStu) Get(key string) (value bool, ok bool) {
	m.RLock()
	defer m.RUnlock()
	value, ok = m.M[key]
	return
}

func (m *ExpAddrMapStu) Delete(key string) {
	m.Lock()
	defer m.Unlock()
	delete(m.M, key)
}
func (m *ExpAddrMapStu) Clean() {
	m.Lock()
	defer m.Unlock()
	m.M = make(map[string]bool)
}
func (m *Page) ParseExpData() (err error) {
	m.ExpDataList = make([]*ExpDataObj, 0)
	if m.ExpData == "" || m.ExpData == "{}" || m.ExpData == "[]" {
		return
	}
	err = json.Unmarshal([]byte(m.ExpData), &m.ExpDataList)
	if err != nil {
		return
	}
	if len(m.ExpDataList) == 0 {
		_, ok := RunningExpPage.Get(m.ID)
		if ok {
			RunningExpPage.Delete(m.ID)
		}
		return
	}
	RunningExpPage.Set(m.ID, m)
	for _, el := range m.ExpDataList {
		if el.Type == "group" {
			for _, item := range el.ItemList {
				if item.Addr != nil {
					ExpAddrMap.Set(*item.Addr)
				}
			}
		}
	}
	return
}

/*
	下面为实验报警需要做判断的每个报警项
	报警内容为： 【画面名】【实验名】【组】【台号】【上下限报警】
	实验超上限报警标识 EXP_ALERT_HIGH_LIMIT_EXCEEDED
	实验超下限报警标识 EXP_ALERT_LOWER_LIMIT_EXCEEDED
*/

type ExpAlarmItemsStu struct {
	Data []*ExpAlertDetail
	sync.RWMutex
}

type ExpAlertDetail struct {
	AlertDetail
	PageName  string `json:"page_name"`  // 画面名
	ExpName   string `json:"exp_name"`   // 实验名
	GroupName string `json:"group_name"` // 组名
	RowName   string `json:"row_name"`   // 台号名
	Alert     string `json:"alert"`      // 计算结果
	Const     string `json:"const"`      // 标识 EXP_ALERT_HIGH_LIMIT_EXCEEDED
}

// ComputeExpAlert 成组实验报警计算函数
// key为组合key, 【画面名】【实验名】【组】【台号】
func ComputeExpAlert() (AlarmDataList []*VarAlert) {
	defer func() {
		if err := recover(); err != nil {
			j_log.LogError([]interface{}{"ComputeExpAlert panic", err})
			fmt.Println(string(debug.Stack()))
		}
	}()
	RunningExpPage.RLock()
	defer RunningExpPage.RUnlock()
	AlarmDataList = make([]*VarAlert, 0) // 待推送的报警消息
	now := tools.GetNow()
	for _, page := range RunningExpPage.M { // 所有画面循环计算一次
	loopExp:
		for _, exp := range page.ExpDataList { // 每个画面中的所有实验循环一次
			if exp.Type == "exp" {
				if exp.UpperLimitValue == nil && exp.LowerLimitValue == nil {
					continue loopExp // 未设置报警值的跳过
				}
				for _, group := range page.ExpDataList {
					if group.Type == "group" && exp.HasGroupName(group.Name) {
					loopRow:
						for _, row := range group.ItemList { // 每个实验关联组中的所有台号循环一次
							if row.Addr == nil {
								continue loopRow // 未设置变量地址的台号跳过
							}
							key := fmt.Sprintf("%s%s%s%s", page.PageName, exp.Name, group.Name, row.Name)
							alertData, err := CheckExpAlert(page, exp, group, row, &key)
							if err != nil {
								j_log.LogWarning([]interface{}{fmt.Sprintf("%s", err)})
								continue loopRow
							}
							lastAlertData, ok2 := AlertDataMap.Get(key)
							if ok2 {
								if alertData.Alert == "" {
									// 内存中有报警信息, 但是当前数据正常
									// 1. 则屏蔽旧报警, 推送新的恢复信息
									//fmt.Printf("内存中有报警信息, 但是当前数据正常, 删除内存中的报警 %s %s\n", alertData.BaseAddr, alertData.Detail)
									alertData.Const = lastAlertData.Const
									alertData.IsRecovery = 1
									alertData.RecoveryTime = &now
									alertData.CreateTime = &now
									AlarmDataList = append(AlarmDataList, alertData)

									lastAlertData.IsRecovery = 1
									lastAlertData.RecoveryTime = &now
									VarAlertQueue.Queue <- lastAlertData

									AlertDataMap.Delete(key)
								} else {
									// 内存中有报警信息, 且当前数据也有报警
									// 1. 报警类型相同, 则忽略
									// 2. 报警类型不同, 则推送屏蔽旧报警, 且推送新报警
									if alertData.Const != lastAlertData.Const {
										// 先把上一次报警恢复
										lastAlertData.IsRecovery = 1
										lastAlertData.RecoveryTime = &now
										VarAlertQueue.Queue <- lastAlertData
										AlarmDataList = append(AlarmDataList, lastAlertData)

										// 再保存并推送本次报警
										alertData.IsRecovery = 0
										alertData.AudioShield = 0
										VarAlertQueue.Queue <- alertData
										AlarmDataList = append(AlarmDataList, alertData)

										AlertDataMap.Set(key, alertData)
									} else {
										//fmt.Printf("内存中有报警信息, 且当前数据也有报警 报警类型一致, 忽略 %s %s\n", alertData.BaseAddr, alertData.Detail)
									}
								}
							} else {
								if alertData.Alert != "" {
									//fmt.Printf("内存中没有报警信息, 此时刚好有报警, 直接记录 %s %s\n", alertData.BaseAddr, alertData.Detail)
									alertData.AudioShield = 0
									alertData.IsRecovery = 0
									VarAlertQueue.Queue <- alertData
									AlarmDataList = append(AlarmDataList, alertData) // 推送报警或恢复
									AlertDataMap.Set(key, alertData)
								}
							}

						}
					}
				}
			}
		}
	}
	return
}

func (m *ExpDataObj) HasGroupName(groupName string) bool {
	for _, row := range m.ItemList {
		if row.Name == groupName {
			return true
		}
	}
	return false
}

// CheckExpAlert 判断实验报警或恢复
func CheckExpAlert(page *Page, exp *ExpDataObj, group *ExpDataObj, row *ExpDataItem, key *string) (alertData *VarAlert, err error) {
	alertData = &VarAlert{
		ProjectID: page.ProjectID,
		T1:        "实验报警",
		T2:        "模拟量报警",
	}
	alertData.Alert = ""

	v, ok := VariablesMap.Get(*row.Addr)
	if !ok {
		err = errors.New(fmt.Sprintf("未找到变量地址对应变量%s", *row.Addr))
		return
	}
	now := tools.GetNow()
	alertData.BaseAddr = *row.Addr
	alertData.BaseFieldName = v.FieldName
	alertData.CreateTime = &now
	detail := ExpAlertDetail{
		PageName:  page.PageName,
		ExpName:   exp.Name,
		GroupName: group.Name,
		RowName:   row.Name,
	}
	detail.AlertAddr = *row.Addr
	detail.AlertFieldName = *row.Addr
	detail.AlertValue = v.Value
	if exp.UpperLimitValue != nil {
		upperValue := *exp.UpperLimitValue
		detail.CompareValue = upperValue
		if v.Value > upperValue {
			jsonB, _ := json.Marshal(&detail)
			alertData.Detail = string(jsonB)
			alertData.Const = "EXP_ALERT_HIGH_LIMIT_EXCEEDED"
			alertData.Alert = fmt.Sprintf("%s超上限报警!", *key)
			at, _ := AlertTypeMap.Get("EXP_ALERT_HIGH_LIMIT_EXCEEDED")
			alertData.Audio = at.Audio
			return
		}
	}
	if exp.LowerLimitValue != nil {
		lowerValue := *exp.LowerLimitValue
		detail.CompareValue = lowerValue
		if v.Value < lowerValue {
			jsonB, _ := json.Marshal(&detail)
			alertData.Detail = string(jsonB)
			alertData.Const = "EXP_ALERT_LOWER_LIMIT_EXCEEDED"
			alertData.Alert = fmt.Sprintf("%s超下限报警!", *key)
			at, _ := AlertTypeMap.Get("EXP_ALERT_LOWER_LIMIT_EXCEEDED")
			alertData.Audio = at.Audio
			return
		}
	}
	jsonB, _ := json.Marshal(&detail)
	alertData.Detail = string(jsonB)
	return
}

func LoadRunningExpPage(projectID int) {
	list := make([]*Page, 0)
	ExpAddrMap.Clean()
	_ = DB.Where("project_id = ?", projectID).Find(&list).Error
	for _, page := range list {
		err := page.ParseExpData()
		if err != nil {
			j_log.LogError([]interface{}{fmt.Sprintf("解析实验数据失败 %s", err)})
			continue
		}
	}
}
